package com.adnaan.backend.common.aspect;

import com.adnaan.backend.common.annotation.RateLimiter;
import com.adnaan.backend.common.exception.ServicesException;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * 限流注解处理切面
 */
@Aspect
@Component
public class RateLimiterAspect {

    private final RedisTemplate<String, Object> redisTemplate;
    private final RedisScript<Long> limitScript;

    public RateLimiterAspect(RedisTemplate<String, Object> redisTemplate, RedisScript<Long> limitScript) {
        this.redisTemplate = redisTemplate;
        this.limitScript = limitScript;
    }

    @Before("@annotation(rateLimiter)")
    public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
        String key = getCombinedKey(rateLimiter.prefix(), point);

        // 检查重复提交
        checkRepeatedSubmit(key, rateLimiter.interval(), rateLimiter.message());

        // 执行限流
        List<String> keys = Collections.singletonList(key);
        Long count = redisTemplate.execute(limitScript, keys, rateLimiter.count(), rateLimiter.time());

        if (count == null || count.intValue() > rateLimiter.count()) {
            throw new ServicesException(rateLimiter.message());
        }
    }

    private void checkRepeatedSubmit(String key, long interval, String message) {
        long now = System.currentTimeMillis();
        String submitKey = key + ":submit";

        Boolean isFirstSet = redisTemplate.opsForValue().setIfAbsent(submitKey, now);

        if (Boolean.FALSE.equals(isFirstSet)) {
            Object lastSubmitTime = redisTemplate.opsForValue().get(submitKey);
            if (lastSubmitTime != null && now - (Long) lastSubmitTime < interval) {
                throw new ServicesException(message);
            }
        }

        redisTemplate.opsForValue().set(submitKey, now);
    }

    private String getCombinedKey(String prefix, JoinPoint point) {
        StringBuilder key = new StringBuilder(prefix);

        // 获取请求信息
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        key.append(request.getRequestURI());

        // 获取方法签名
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        key.append(":").append(method.getName());

        return key.toString();
    }
}